﻿using CC.Common;
using CC.Extending;
using System;
using System.Collections.Generic;

using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;

namespace CcMath
{
    public static class ExtenderFunctions
    {
        /// <summary>
        /// 按新的步长插值重新生成数据
        /// 
        /// </summary>
        /// <param name="src"></param>
        /// <param name="step"></param>
        /// <param name="base10">是否归到10的整数倍，是则取10的整数倍天花板值和地板值。否为取上下限的天花板值和地板值</param>
        /// <returns></returns>
        public static Point32[] RegenerateDatapoints(this Point32[] src, double step, bool base10=true )
        {
            //1. 最小最大角取整
            double xMin = Math.Floor(src.Min(p => p.X));
            double xMax = Math.Ceiling(src.Max(p => p.X));
            if(base10)
            {
                xMin = Math.Floor(xMin / 10) * 10;
                xMax = Math.Ceiling(xMax / 10) * 10;
            }
            //2.插值产生新数据点
            int length = (int)((xMax - xMin) / step) + 1;
            Point32[] points = new Point32[length];
            for (int i = 0; i < length; i++)
            {
                points[i] = MyMath.Interpolate1(src, (float)(xMin + i * step));
            }
            return points;
        }
        public static List<double>Zoom(this IEnumerable<float> src,double scale)
        {
            List<double> list = new List<double>();
            foreach (var item in src)
            {
                list.Add(item * scale);
            }
            return list;
        }
        public static List<double> Zoom(this IEnumerable<double> src, double scale)
        {
            List<double> list = new List<double>();
            foreach (var item in src)
            {
                list.Add(item * scale);
            }
            return list;
        }
        public static List<double> FloatArrayToDouble(this IEnumerable<float> src)
        {
            List<double> list = new List<double>();
            foreach (var item in src)
            {
                list.Add(item);
            }
            return list;
        }
        public static List<float> DoubleArrayToFloat(this IEnumerable<double> src)
        {
            List<float> list = new List<float>();
            foreach (var item in src)
            {
                list.Add((float)item);
            }
            return list;
        }
        /// <summary>
        /// 寻找周期性数列的元素的下标
        /// </summary>
        /// <param name="dataAscend">按x值从小到大排列好的数组</param>
        /// <param name="x">x值</param>
        /// <param name="period">x的周期，默认为360度</param>
        /// <returns></returns>
        public static int IndexOfPeriod(this IEnumerable<Point32> dataAscend,double x,double period=360)
        {
            Point32[] array = dataAscend.ToArray();
            float xMin = array.Min(a => a.X);
            float xMax = array.Max(a => a.X);
             if(x<xMin)
            {
                return IndexOfPeriod(array, x + period, period);
            }
            else if (x>xMax)
            {
                return IndexOfPeriod(array, x - period, period);
            }
            else
            {
                var point = array.First(a => a.X >= x);
                return Array.IndexOf(array, point);
            }
        }
        /// <summary>
        /// 按给定步长重新生成序列
        /// </summary>
        /// <param name="points"></param>
        /// <param name="newStep">新步长</param>
        /// <param name="sortBeforeGenerate">在生成序列前是否要先排序</param>
        /// <returns></returns>
        public static Point32[] ReStep(this Point32[] points, double newStep,bool sortBeforeGenerate=false)
        {
            float min = points.Min(p => p.X);
            float max = points.Max(p => p.X);
           if(sortBeforeGenerate)  points = points.OrderBy(p => p.X).ToArray();
            List<Point32> list = new List<Point32>();
            for (float x = min; x <= max; x+=(float)newStep)
            {
                var point = MyMath.Interpolate1(points, x);
                list.Add(point);
            }
            return list.ToArray();
        }
        /// <summary>
        /// 取出指定角度的值，角度按指定的周期
        /// </summary>
        /// <param name="points">按x值从小到大排列好的数组</param>
        /// <param name="x">x值</param>
        /// <param name="period">x的周期，默认为360度</param>
        /// <returns></returns>
        public static float GetValue(this Point32[] points,double x,double period=360)
        {
            int index = points.IndexOfPeriod(x, period);
            return points[index].Y;
        }
        /// <summary>
        /// 修改指定角度的值，角度按指定的周期
        /// </summary>
        /// <param name="points">按x值从小到大排列好的数组</param>
        /// <param name="x">x值</param>
        /// <param name="value"></param>
        /// <param name="period">x的周期，默认为360度</param>
        public static void SetValue(this Point32[] points,double x,double value, double period = 360)
        {
            int index = points.IndexOfPeriod(x, period);
            points[index].Y = (float)value;
        }
        /// <summary>
        /// 取数列中的指定索引的元素，允许下标超限。
        /// 本函数将数列视为周期性数列（头尾相连），如果指定的索引超限(小于0或者大于count-1)，则按+或-周期后取数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <param name="index">要提取的元素的下标，允许小于0或者大于Count-1</param>
        /// <returns></returns>
        public static T GetElementPeriod<T>(this IEnumerable<T> array,int index)
        {
            if (index < 0)
            {
                return GetElementPeriod(array, array.Count() + index);
            }
            else if(index>=array.Count())
            {
                return GetElementPeriod(array, index - array.Count());
            }
            else
            {
                return array.ToArray()[index];
            }
        }
        public static Point32[] SubArrayPeriod(this IEnumerable<Point32> array, double startAngle, double stopAngle)
        {
            int startIndex = IndexOfPeriod(array, startAngle);
            int stopIndex= IndexOfPeriod(array, stopAngle);
            return SubArrayPeriod(array, startIndex, stopIndex);
        }
        /// <summary>
        /// 获取数列中指定范围的子数列，允许下标超限
        /// 本函数将数列视为周期性数列（头尾相连），如果指定的索引超限(小于0或者大于count-1)，则按+或-周期后取子数列
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <param name="startIndex">要提取的子元素集的下索引，允许小于0</param>
        /// <param name="stopIndex">要提取的子元素集的上索引，允许大于Count-1</param>
        /// <returns></returns>
        public static T[] SubArrayPeriod<T>(this IEnumerable<T>array,int startIndex,int stopIndex)
        {
            if (startIndex < 0) 
            {
              return  array.SubArrayPeriod( startIndex + array.Count(), stopIndex);
                //return SubArrayPeriod(); 
            }
            else if (stopIndex >= array.Count())
            {
                return array.SubArrayPeriod(startIndex, stopIndex - array.Count());
                //return SubArrayPeriod(array, startIndex, stopIndex - array.Count());
            }
            else
            {
                return MyMath.SubArray(array.ToArray(), startIndex, stopIndex);
            }
        }
        /// <summary>
        /// PointF[]型数列旋转指定的索引数：假设数据为首尾相接。X不变，Y值旋转
        /// </summary>
        /// <param name="value"></param>
        /// <param name="rotating">要旋转的索引数</param>
        /// <param name="antiClock">是否逆时针</param>
        /// <returns></returns>
        public static Point32[] RoatateValue( this Point32[] value, int rotating, bool antiClock=false)
        {
            float[] yData = value.Select(d => d.Y).ToArray();
            yData = MyMath.RotateArray(yData, rotating, antiClock);
            Point32[] data = new Point32[yData.Length];
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = new Point32(value[i].X, yData[i]);
            }
            return data;
        }
        /// <summary>
        /// 方向图旋转指定的角度
        /// </summary>
        /// <param name="value"></param>
        /// <param name="rotateAngle">要旋转的角度，正数：逆时针，负数：顺时针</param>
        /// <returns></returns>
        public static Point32[] RotateValue(this Point32[] value, double rotateAngle)
        {
            float range = value.Max(v => v.X) - value.Min(v => v.X);
            bool antiClock = rotateAngle > 0 ? true : false;

            int rotating = (int)Math.Ceiling(Math.Abs( rotateAngle)*value.Length / range);
            return RoatateValue(value, rotating, antiClock);
        }
        /// <summary>
        /// 翻转数据: 阵列的数据头尾对调 Array[i]=Array[N-i]
        /// </summary>
        /// <param name="data"></param>
        /// <param name="accordingToXValue">根据X坐标值来翻转，否则根据索引翻转</param>
        /// <returns></returns>
        public static Point32[] FlipArray(this Point32[] data,bool accordingToXValue=true)
        {
            Point32[] points = new Point32[data.Length];
            if(accordingToXValue)
            {

                float xMid = (data.Max(d => d.X) + data.Min(d => d.X)) / 2;
                for (int i = 0; i < points.Length; i++)
                {
                    float x = xMid - data[i].X;
                    points[i] = new Point32(x, data[i].Y);
                }
                return points.OrderBy(p => p.X).ToArray();
            }
            else
            {
                for (int i = 0; i < points.Length / 2; i++)
                {
                    points[i].X = data[i].X;
                    points[i].Y = data[data.Length - i - 1].Y;
                    points[data.Length - i - 1].X = data[data.Length - i - 1].X;
                    points[data.Length - i - 1].Y = data[i].Y;
                }
                return points;
            }
            
        }
       
        /// <summary>
        /// 矩阵转置: array[n][m]-->array[m][n]
        /// 
        /// </summary>
        /// <param name="Data"></param>
        /// <returns></returns>
        public static Point32[][] Transpose(this Point32[][] value)
        {
            int N = value.Length;
            int M = value[0].Length;
            Point32[][] array = new Point32[M][];
            for (int m = 0; m < M; m++)
            {
                array[m] = new Point32[N];
                for (int n = 0; n < N; n++)
                {
                    array[m][n] = value[n][m];
                }
            }
            return array;
        }
        /// <summary>
        /// 矩阵转置: array[n][m]-->array[m][n]
        /// 
        /// </summary>
        /// <param name="Data"></param>
        /// <returns></returns>
        public static double[][] Transpose(this double[][] value)
        {
            int N = value.Length;
            int M = value[0].Length;
            double[][] array = new double[M][];
            for (int m = 0; m < M; m++)
            {
                array[m] = new double[N];
                for (int n = 0; n < N; n++)
                {
                    array[m][n] = value[n][m];
                }
            }
            return array;
        }
        /// <summary>
        /// 转置array[n][m]-->array[m][n]
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static float[][] Transpose(this float[][] value)
        {
            int N = value.Length;
            int M = value[0].Length;
            float[][] array = new float[M][];
            for (int m = 0; m < M; m++)
            {
                array[m] = new float[N];
                for (int n = 0; n < N; n++)
                {
                    array[m][n] = value[n][m];
                }
            }
            return array;
        }
        public static Complex[] SubArray(this Complex[] data, int start, int end)
        {
            List<Complex> list = new List<Complex>();
            if (end >= data.Length) end = data.Length;
            for (int i = start; i < end; i++)
            {
                list.Add(data[i]);
            }
            return list.ToArray();
        }
        public static double[] SubArray(this double[] data, int start, int end)
        {
            List<double> list = new List<double>();
            if (end >= data.Length) end = data.Length;
            for (int i = start; i < end; i++)
            {
                list.Add(data[i]);
            }
            return list.ToArray();
        }
        /// <summary>
        /// 返回数据阵列中大于等于给定值的第一个数据的索引
        /// 要求数据必须按x升序排列
        /// </summary>
        /// <param name="points">数据阵列</param>
        /// <param name="x">指定值</param>
        /// <returns>大于等于给定值的第一个数据的索引；如果找不到，则指向最后一个数据的索引</returns>
        public static int IndexOf(this Point32[] points, double x)
        {
            for (int i = 0; i < points.Length; i++)
            {
                if (points[i].X >= x) return i;
            }
            return points.Length - 1;
        }
        /// <summary>
        /// 要求按从小到大排列好的数据
        /// </summary>
        /// <param name="points"></param>
        /// <param name="x"></param>
        /// <returns></returns>
        public static int IndexOf(this double[] points, double x)
        {
            for (int i = 0; i < points.Length; i++)
            {
                if (points[i]>= x) return i;
            }
            return points.Length - 1;
        }

        public static int IndexOf(this float[] points, double x)
        {
            for (int i = 0; i < points.Length; i++)
            {
                if (points[i] >= x) return i;
            }
            return points.Length - 1;
        }

        /// <summary>
        /// 归一化处理
        /// 如果是dB值，则每个数据-最大值，如果是线性值，则每个数据/最大值
        /// </summary>
        /// <param name="points"></param>
        /// <param name="maxValue">归一化到此值</param>
        /// <returns></returns>
        public static Point32[] NormalizeTo(this Point32[] points, bool dBAsUnit = true, float maxValue = float.MaxValue)
        {
            if (maxValue == float.MaxValue)
            {
                maxValue = points.Max(p => p.Y);
            }
            Point32[] nPoints = new Point32[points.Length];
            if (dBAsUnit)
            {
                for (int i = 0; i < nPoints.Length; i++)
                {
                    nPoints[i].Y -= maxValue;
                }
            }
            else
            {

                for (int i = 0; i < nPoints.Length; i++)
                {
                    nPoints[i].Y /= maxValue;
                }
            }
            return nPoints;
        }
        /// <summary>
        /// 将字符串转换为PointF型，如果无法转换则返回0
        /// </summary>
        /// <param name="s">要求s为以逗号或分号分隔的两个数字</param>
        /// <returns>成功返回转换出来的值，失败则返回PointF.Empty</returns>
        public static Point32 ToPointF(this string s)
        {
            Point32 point = Point32.Empty;
            string[] segs = s.Split(new char[] {'(',')', ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
            if (segs.Length >= 2)
            {
                if (float.TryParse(segs[0], out float x))
                {
                    point.X = x;
                }
                if (float.TryParse(segs[1], out float y))
                {
                    point.Y = y;
                }
            }
            return point;
        }
        
        /// <summary>
        /// 将数据点周期化。
        /// 例如周期化为以[0,360], [-180,180]为周期
        /// </summary>
        /// <param name="points">数据阵列</param>
        /// <param name="periodicity">周期数，如360，2*pi等</param>
        /// <param name="minValue">周期内最小值，如0，-180,-pi等</param>
        /// <returns>将point.X规整到周期内</returns>
        public static Point32[] PeriodizeTo(this Point32[] points, float periodicity, float minValue = 0)
        {
            Point32[] nPoints = new Point32[points.Length];
            for (int i = 0; i < points.Length; i++)
            {

                nPoints[i].X = MyMath.LimitToPeriod(points[i].X, periodicity, minValue);
            }
            return nPoints;
        }
        /// <summary>
        /// 以Value为基准寻找最大值
        /// </summary>
        /// <param name="datas"></param>
        /// <returns>返回最大点值[X,Y,Value]</returns>
        public static PointValueF Max(this IEnumerable<PointValueF> datas)
        {
            PointValueF max = datas.LastOrDefault();
            foreach (var d in datas)
            {
                if (d.Value > max.Value) max = d;
            }
            return max;
        }

        public static PointValueC Max(this IEnumerable<PointValueC> datas)
        {
            PointValueC max = datas.FirstOrDefault();
            foreach (var d in datas)
            {
                if (d.Value.Magnitude > max.Value.Magnitude) max = d;
            }
            return max;
        }
        public static double StandardDeviation(this IEnumerable<double> values)
        {
            double mean = values.Average();
            double[] array = values.ToArray();
            double sum = 0;
            for (int i = 0; i < array.Length; i++)
            {
                sum += (array[i] - mean) * (array[i] - mean);
            }
            return Math.Sqrt(sum / array.Length);
        }
        public static double StandardDeviation(this IEnumerable<float> values)
        {
            double mean = values.Average();
            float[] array = values.ToArray();
            double sum = 0;
            for (int i = 0; i < array.Length; i++)
            {
                sum += (array[i] - mean) * (array[i] - mean);
            }
            return Math.Sqrt(sum / array.Length);
        }
        /// <summary>
        /// 复数上叠加相移量
        /// </summary>
        /// <param name="number"></param>
        /// <param name="shift_deg"></param>
        /// <returns></returns>
        public static Complex PhaseShift_deg(this Complex number,double shift_deg)
        {
            return PhaseShift(number, shift_deg.ToRad());
        }
        /// <summary>
        /// 复数上叠加相移量
        /// </summary>
        /// <param name="number"></param>
        /// <param name="shift_rad">以弧度为单位</param>
        /// <returns></returns>
        public static Complex PhaseShift(this Complex number, double shift_rad)
        {
            return number * new Complex(Math.Cos(shift_rad), Math.Sin(shift_rad));
        }
        public static double Magnitude_dB(this Complex number)
        {
            return number.Magnitude.MagToDB();
        }
        public static double Vswr(this Complex number)
        {
            return (1 + number.Magnitude) / (1 - number.Magnitude);
        }
        public static double[] ToVswr(this Complex[] data)
        {
            double[] vs = new double[data.Length];
            for (int i = 0; i < vs.Length; i++)
            {
                vs[i] = data[i].Vswr();
            }
            return vs;
        }
        public static double Phase_deg(this Complex number)
        {
            return number.Phase.ToDeg();
        }
        public static double Magnitude_Power(this Complex number)
        {
            return number.Magnitude * number.Magnitude;
        }
        public static string ToStringDBDeg(this Complex number,int decimals = 2)
        {
            double db = Math.Round(number.Magnitude.MagToDB(), decimals);
            double deg = Math.Round(number.Phase.ToDeg(), decimals);
            return $"({db},{deg})";
        }

        public static double ToRad(this double degree, int decimals = -1)
        {
            var value = degree * Math.PI / 180f;
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rad"></param>
        /// <param name="decimals">小数点位数，小于0表示控制</param>
        /// <returns></returns>
        public static double ToDeg(this double rad, int decimals=-1)
        {
            if(decimals<0)
                return rad * 180f / Math.PI;
            else
            {
                return Math.Round(rad * 180f / Math.PI, decimals);
            }
        }
        public static float ToRad(this float degree, int decimals = -1)
        {
            var value = (float)(degree * Math.PI / 180f);
            if (decimals < 0) return value;
            else return (float) Math.Round(value, decimals);
        }
        public static float ToDeg(this float rad, int decimals = -1)
        {
            var value = (float)(rad * 180f / Math.PI);
            if (decimals < 0) return value;
            else return (float) Math.Round(value, decimals);
        }
        public static double MagToDB(this double mag, int decimals = -1)
        {
            var value = 20 * Math.Log10(mag);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double MagToDB(this float mag, int decimals = -1)
        {
            var value = 20 * Math.Log10(mag);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double DBToVSWR(this float db, int decimals = -1)
        {
            var value = MagToVSWR(DBToMag(db));
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double MagToVSWR(this float mag, int decimals = -1)
        {
            var value = (1 + Math.Abs(mag)) / (1 - Math.Abs(mag));
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double MagToVSWR(this double mag, int decimals = -1)
        {
            var value = (1 + Math.Abs(mag)) / (1 - Math.Abs(mag));
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double DBToMag(this double dB, int decimals = -1)
        {
            var value = Math.Pow(10, dB / 20f);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double DBToMag(this float dB, int decimals = -1)
        {
            var value = Math.Pow(10, dB / 20f);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double PowerToDB(this double mag, int decimals = -1)
        {
            var value = 10 * Math.Log10(mag);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double DBToPower(this double dB, int decimals = -1)
        {
            var value = Math.Pow(10, dB / 10f);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double PowerToDB(this float mag, int decimals = -1)
        {
            var value = 10 * Math.Log10(mag);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static double DBToPower(this float dB, int decimals = -1)
        {
            var value = Math.Pow(10, dB / 10f);
            if (decimals < 0) return value;
            else return Math.Round(value, decimals);
        }
        public static string ToCommaString(this IEnumerable<Complex> array, char splitor = ';')
        {
            string str = "";
            foreach (var item in array)
            {
                str += splitor.ToString() + item;
            }
            return str.Substring(1);
        }
        public static Complex TextToComplex(this string text)
        {
            Complex point = new Complex(0, 0);
            string[] segs = text.Split(new char[] { '(', ',', ' ', ';', ')' }, StringSplitOptions.RemoveEmptyEntries);
            if (double.TryParse(segs.FirstOrDefault(), out double x) && double.TryParse(segs.Last(), out double y))
            {
                point = new Complex(x, y);
            }
            return point;
        }
       
        public static Complex[] TextToComplexFArray(this string text, char splitor = ';')
        {
            string[] segs = text.Split(new char[] { splitor }, StringSplitOptions.RemoveEmptyEntries);
            if (segs.Length <= 0) return null;
            Complex[] points = new Complex[segs.Length];
            for (int i = 0; i < points.Length; i++)
            {
                points[i] = segs[i].TextToComplex();
            }

            return points;
        }
    }
}
